(5.4-5.9由于开题事宜断更,即日起恢复更新)
前言
当写下一个constructor时我们可以设定class members的初值:它们要么经由Member Initialization List,要么在construtor内部处理(除了4种情况必须使用member initialization list)。
何时必须使用member initialization list
以下四种情况必须使用member initialization list:
- 初始化一个reference member
- 初始化一个const member
- 调用一个base class的constructor,而它拥有一组参数
- 当调用一个member class的constructor,而它拥有一组参数
member initialization list详解
不使用member initialization list
考虑如下程序:1
2
3
4
5
6
7
8
9class Word{
String _name;
int _cnt;
public:
Word(){
_name=0;
_cnt=0;
}
};
该程序能够正确编译并执行,只是效率偏低。编译器在执行上述程序扩张代码使之生成一个临时对象,如下所示:1
2
3
4
5
6
7Word::Word(/*this poniter*/){
_name.String::String();
String temp = String(nullptr);
_name.String::operator=(temp);
temp.String::~String();
_cnt=0;
}
为了保证高效,我们应当做到对任何初始化操作都执行member initialization list。
member initialization list的作用
当构造函数中出现member initialization list后,编译器会一一操作member initialization list,以member声明次序(而非list内部次序)在constructor之内安插初始化操作,次序问题极易引发危险操作,例如:1
2
3
4
5
6class X{
int i;
int j;
public:
X(int val):j(val),i(j) {}
};
将会被扩张为:1
2
3
4X::X(int val){
i=j;//初始化失败
j=bal;
}
member initialization list的疑难点
二探次序
如果member initialization list中的项目被安插到constructor中,会继续保存声明次序吗?1
2
3
4X::X(int val)
:j(val){
i=j;
}
j的初始化操作会安插在explicit user assignment之前或者是之后?答案是initialization list的项目总会被放在explicit user code之前。
initialization list与member function
能否调用一个member function以设定一个member的初值?1
2
3X::X(int val)
:i(xfoo(val)),j(val)//xfoo是X的一个member function
{}
答案是可行的。但是…务必使用“存在于constructor体内的一个member”,而非“存在于member initialization list中的member”,来为另一个member设定初值。因为我们无法了解xfoo()对object的依赖性,在确保了xfoo()在constructor内部之后,对于“究竟是哪一个member在xfoo()执行时被设立初值”,就不会造成歧义。
member function的使用是合法的,是建立在和此object相关的this指针已经就位(我们暂且忽略与该member function相关的member),此时原代码大致被扩张为:1
2
3
4X::X(/*this pointer*/){
i = this->xfoo(val);
j = val;
}
如果一个derived class member function被调用,其返回值作为base class constructor的参数呢?1
2
3
4
5
6
7class FooBar:public X{
int _fval;
public:
int fval() {return _fval;}
FooBar(int val):_fval(val),X(fval()) {}
...
}
其扩张结果如下:1
2
3
4FooBar::FooBar(/*this pointer*/){
X::X(this,this->fval());
_fval =val;
}
显然,这确实不合时宜。
总结
简单地说,编译器会对initialization list一一处理并且重新排序,以反映出member的声明次序。它会安插部分代码到constructor体内,并置于任何explicit user code之前。